﻿
#include "Fact.h"

#include <QDebug>
#include <QtMath>
#include <limits>
#include <cmath>
#include <QQmlEngine>
#include <QTime>

const int ZERO = 0;
const int NEGATIVE_ONE = -1;
const int ONE = 1;

Fact::Fact(QObject *parent)
    : QObject{parent}
    , type_(valueTypeInt32)
    , rawValue_()
    , name_("")
    , decimalPlaces_(kDefaultDecimalPlaces_)
    , rawMax_(maxForType_())
    , rawMin_(minForType_())
    , units_("")
{
    QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
}
//-------------------------------------------------------------
Fact::Fact(QString  name,ValueType_t type,QObject *parent)
    : QObject{parent}
    , type_(type)
    , rawValue_()
    , name_(name)
    , decimalPlaces_(kDefaultDecimalPlaces_)
    , rawMax_(maxForType_())
    , rawMin_(minForType_())
    , units_("")
{
    QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
}
//----------------------------------------------------------------------
const Fact& Fact::operator=(const Fact& other)
{
    name_              = other.name_;
    rawValue_          = other.rawValue_;
    type_              = other.type_;
    decimalPlaces_     = other.decimalPlaces_;
    rawMax_            = other.rawMax_;
    rawMin_            = other.rawMin_;
    units_             = other.units_;
    return *this;
}
//----------------------------------------------------------------------
Fact::Fact(const Fact& other, QObject* parent)
    : QObject(parent)
{
    *this = other;
}
//---------------------------------------------------------------------
QString Fact::valueString()
{
    return variantToString_(value(), decimalPlaces());
}
//-------------------------------------------------------------------------
QString Fact::variantToString_(const QVariant& variant, int decimalPlaces) const
{
    QString valueString{};
    switch (type()) {
    case valueTypeUint8:
    case valueTypeInt8:
    case valueTypeUint16:
    case valueTypeInt16:
    case valueTypeUint32:
    case valueTypeInt32:
    {
        int iValue = variant.toInt();
        valueString = QString::number(iValue);
    }
    break;
    case Fact::valueTypeFloat:
    {
      float fValue = variant.toFloat();
      if (qIsNaN(fValue)) {
          valueString = QStringLiteral("--.--");
      } else {
          valueString = QString("%1").arg(fValue, 0, 'f', decimalPlaces);
      }
    }
    break;
    case Fact::valueTypeDouble:
    {
      double dValue = variant.toDouble();
      if (qIsNaN(dValue)) {
          valueString = QStringLiteral("--.--");
      } else {
          valueString = QString("%1").arg(dValue, 0, 'f', decimalPlaces);
      }
    }
    break;
    case Fact::valueTypeBool:
      valueString = variant.toBool() ? tr("1") : tr("0");
      break;
    case Fact::valueTypeTimeInSeconds:
    {
      double dValue = variant.toDouble();
      if (qIsNaN(dValue)) {
          valueString = QStringLiteral("--:--:--");
      } else {
          QTime time(0, 0, 0, 0);
          time = time.addSecs(dValue);
          valueString = time.toString(QStringLiteral("hh:mm:ss"));
      }
    }
    break;
    break;
    default:
      valueString = variant.toString();
      break;
    }
    return valueString;
}

//--------------------------------------------------------------------------
QVariant Fact::maxForType_(void) const
{
    switch (type_) {
    case valueTypeUint8:
      return QVariant(std::numeric_limits<unsigned char>::max());
    case valueTypeInt8:
      return QVariant(std::numeric_limits<signed char>::max());
    case valueTypeUint16:
      return QVariant(std::numeric_limits<unsigned short int>::max());
    case valueTypeInt16:
      return QVariant(std::numeric_limits<short int>::max());
    case valueTypeUint32:
      return QVariant(std::numeric_limits<uint32_t>::max());
    case valueTypeInt32:
      return QVariant(std::numeric_limits<int32_t>::max());
    case valueTypeUint64:
      return QVariant((qulonglong)std::numeric_limits<uint64_t>::max());
    case valueTypeInt64:
      return QVariant((qlonglong)std::numeric_limits<int64_t>::max());
    case valueTypeFloat:
      return QVariant(std::numeric_limits<float>::max());
    case valueTypeTimeInSeconds:
    case valueTypeDouble:
      return QVariant(std::numeric_limits<double>::max());
    case valueTypeString:
      return QVariant();
    case valueTypeBool:
      return QVariant(1);
    case valueTypeCustom:
      return QVariant();
    }
    return QVariant();
}
//----------------------------------------------------------------------------
QVariant Fact::minForType_(void) const
{
    switch (type_) {
    case valueTypeUint8:
      return QVariant(std::numeric_limits<unsigned char>::min());
    case valueTypeInt8:
      return QVariant(std::numeric_limits<signed char>::min());
    case valueTypeUint16:
      return QVariant(std::numeric_limits<unsigned short int>::min());
    case valueTypeInt16:
      return QVariant(std::numeric_limits<short int>::min());
    case valueTypeUint32:
      return QVariant(std::numeric_limits<uint32_t>::min());
    case valueTypeInt32:
      return QVariant(std::numeric_limits<int32_t>::min());
    case valueTypeUint64:
      return QVariant((qulonglong)std::numeric_limits<uint64_t>::min());
    case valueTypeInt64:
      return QVariant((qlonglong)std::numeric_limits<int64_t>::min());
    case valueTypeFloat:
      return QVariant(-std::numeric_limits<float>::max());
    case valueTypeDouble:
      return QVariant(-std::numeric_limits<double>::max());
    case valueTypeString:
      return QVariant();
    case valueTypeBool:
      return QVariant(0);
    case valueTypeTimeInSeconds:
      return QVariant(0.0);
    case valueTypeCustom:
      return QVariant();
    }
    return QVariant();
}
//-------------------------------------------------------------------------------
bool Fact::isInMinLimit(const QVariant& variantValue) const
{
    switch (type_) {
    case valueTypeUint8:
      return rawMin_.value<unsigned char>() <= variantValue.value<unsigned char>();
    case valueTypeInt8:
      return rawMin_.value<signed char>() <= variantValue.value<signed char>();
    case valueTypeUint16:
      return rawMin_.value<unsigned short int>() <= variantValue.value<unsigned short int>();
    case valueTypeInt16:
      return rawMin_.value<short int>() <= variantValue.value<short int>();
    case valueTypeUint32:
      return rawMin_.value<uint32_t>() <= variantValue.value<uint32_t>();
    case valueTypeInt32:
      return rawMin_.value<int32_t>() <= variantValue.value<int32_t>();
    case valueTypeUint64:
      return rawMin_.value<uint64_t>() <= variantValue.value<uint64_t>();
    case valueTypeInt64:
      return rawMin_.value<int64_t>() <= variantValue.value<int64_t>();
    case valueTypeFloat:
      return qIsNaN(variantValue.toFloat()) || rawMin_.value<float>() <= variantValue.value<float>();
    case valueTypeDouble:
      return qIsNaN(variantValue.toDouble()) || rawMin_.value<double>() <= variantValue.value<double>();
    default:
      return true;
    }
    return true;
}
//------------------------------------------------------------------------------------------
bool Fact::isInMaxLimit(const QVariant& variantValue) const
{
    switch (type_) {
    case valueTypeUint8:
      return rawMax_.value<unsigned char>() >= variantValue.value<unsigned char>();
    case valueTypeInt8:
      return rawMax_.value<signed char>() >= variantValue.value<signed char>();
    case valueTypeUint16:
      return rawMax_.value<unsigned short int>() >= variantValue.value<unsigned short int>();
    case valueTypeInt16:
      return rawMax_.value<short int>() >= variantValue.value<short int>();
    case valueTypeUint32:
      return rawMax_.value<uint32_t>() >= variantValue.value<uint32_t>();
    case valueTypeInt32:
      return rawMax_.value<int32_t>() >= variantValue.value<int32_t>();
    case valueTypeUint64:
      return rawMax_.value<uint64_t>() >= variantValue.value<uint64_t>();
    case valueTypeInt64:
      return rawMax_.value<int64_t>() >= variantValue.value<int64_t>();
    case valueTypeFloat:
      return qIsNaN(variantValue.toFloat()) || rawMax_.value<float>() >= variantValue.value<float>();
    case valueTypeDouble:
      return qIsNaN(variantValue.toDouble()) || rawMax_.value<double>() >= variantValue.value<double>();
    default:
      return true;
    }
    return true;
}
//---------------------------------------------------------------------------------------
void Fact::setValue(const QVariant &value)
{
    QVariant    typedValue;
    QString     errorString;
    if (convertAndValidateRaw(value, true, typedValue, errorString)) {
      if (typedValue != rawValue_) {
          rawValue_.setValue(typedValue);
          emit valueChanged(rawValue_); 
          emit valueStringChanged(valueString());
      }
    }
    else{
      emit errorMsg(QString("setvalue error:%1 , %2").arg(name_).arg(errorString));
    }
}
//-------------------------------------------------------------------------------
void Fact::setDecimalPlaces(int decimalPlaces)
{
    decimalPlaces_ = decimalPlaces;
    emit decimalChanged();
}
//-------------------------------------------------------------------------------
void Fact::setMax(const QVariant & rawMax)
{
    if (isInMaxLimit(rawMax)) {
      rawMax_ = rawMax;
      emit maxChanged();
    } else {
      emit errorMsg(QString("set max above allowable value: %1").arg(name_));
      rawMax_ = maxForType_();
    }
}
//--------------------------------------------------------------------------------
void Fact::setMin(const QVariant & rawMin)
{
    if (isInMinLimit(rawMin)) {
      rawMin_ = rawMin;
      emit minChanged();
    } else {
      emit errorMsg(QString("set min below allowable value: %1").arg(name_));
      rawMin_ = minForType_();
    }
}
//------------------------------------------------------------------------------
void Fact::setUnits(const QString & units)
{
    units_  = units;
    emit unitsChanged();
}
//-----------------------------------------------------------------------------
void Fact::setProperty(bool state)
{
    property_ = state;
    emit propertyChanged();
}
//-------------------------------------------------------------------------------
bool Fact::convertAndValidateRaw(const QVariant& rawValue, bool convertOnly, QVariant& typedValue, QString& errorString)
{
  bool convertOk = false;

  errorString.clear();

  switch (type()) {
  case Fact::valueTypeInt8:
  case Fact::valueTypeInt16:
  case Fact::valueTypeInt32:
      typedValue = QVariant(rawValue.toInt(&convertOk));
      if (!convertOnly && convertOk) {
              if (!isInLimit<int32_t>(typedValue)) {
                  errorString = tr("Value must be within %1 and %2").arg(min().toInt()).arg(max().toInt());
              }
      }
      break;
  case Fact::valueTypeInt64:
      typedValue = QVariant(rawValue.toLongLong(&convertOk));
      if (!convertOnly && convertOk) {
              if (!isInLimit<int64_t>(typedValue)) {
                  errorString = tr("Value must be within %1 and %2").arg(min().toInt()).arg(max().toInt());
              }
      }
      break;
  case Fact::valueTypeUint8:
  case Fact::valueTypeUint16:
  case Fact::valueTypeUint32:
      typedValue = QVariant(rawValue.toUInt(&convertOk));
      if (!convertOnly && convertOk) {
              if (!isInLimit<uint32_t>(typedValue)) {
                  errorString = tr("Value must be within %1 and %2").arg(min().toUInt()).arg(max().toUInt());
              }
      }
      break;
  case Fact::valueTypeUint64:
      typedValue = QVariant(rawValue.toULongLong(&convertOk));
      if (!convertOnly && convertOk) {
              if (!isInLimit<uint64_t>(typedValue)) {
                  errorString = tr("Value must be within %1 and %2").arg(min().toUInt()).arg(max().toUInt());
              }
      }
      break;
  case Fact::valueTypeFloat:
      typedValue = QVariant(rawValue.toFloat(&convertOk));
      if (!convertOnly && convertOk) {
              if (!isInLimit<float>(typedValue)) {
                  errorString = tr("Value must be within %1 and %2").arg(min().toDouble()).arg(max().toDouble());
              }
      }
      break;
  case Fact::valueTypeTimeInSeconds:
  case Fact::valueTypeDouble:
      typedValue = QVariant(rawValue.toDouble(&convertOk));
      if (!convertOnly && convertOk) {
              if (!isInLimit<double>(typedValue)) {
                  errorString = tr("Value must be within %1 and %2").arg(min().toDouble()).arg(max().toDouble());
              }
      }
      break;
  case Fact::valueTypeString:
      convertOk = true;
      typedValue = QVariant(rawValue.toString());
      break;
  case Fact::valueTypeBool:
      convertOk = true;
      typedValue = QVariant(rawValue.toBool());
      break;
  case Fact::valueTypeCustom:
      convertOk = true;
      typedValue = QVariant(rawValue.toByteArray());
      break;
  }
  if (!convertOk) {
      errorString += tr("Invalid number");
  }
  return convertOk && errorString.isEmpty();
}
//------------------------------------------------------------------------------------------------------------




